package mantel.metrics;

import genome.CGP;
import genome.Genotype;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import mantel.DistanceMatrix;

public class InverseProbabilityMetric extends Metric {

	@Override
	public double dist(Genotype g1, Genotype g2) {
		if(g1 instanceof CGP){
			return calcExpectedDistance((CGP)g1,(CGP)g2);
		}
		else {
			System.out.println("Inverse Probability Metric not yet implemented for non-CGP classes");
			System.exit(0);
			return -1.0; //Will never reach this point.
		}
	}

	@Override
	public DistanceMatrix dist(ArrayList<Genotype> gSpace) {
		DistanceMatrix dm = new DistanceMatrix(gSpace.size());
		int count=0;
		
		LinkedList<Genotype> gSpaceLinkedList = new LinkedList<Genotype>(gSpace);
		
		int sz = gSpaceLinkedList.size();
		
		ArrayList<Genotype> gTemp = new ArrayList<Genotype>();
		
		for (int i = 0; i < sz; i++) {
			Genotype g1 = gSpaceLinkedList.removeFirst();
			for (Genotype g2 : gTemp) {
				dm.set(count, dist(g1,g2));
				count = count+1;
			}
			gTemp.add(g1);
		}
		return dm;
	}
	
	public ArrayList<Integer> diffConnectIndices(CGP g1, CGP g2){
		ArrayList<Integer> connectIndices = new ArrayList<Integer>();
		
		for(int i=0;i<g1.size;i++){
			if((i+1)%(g1.arity+1)!=0){ //if not a function allele i.e. a connection allele
				if(g1.genome[i]!=g2.genome[i]){ //if connections do not match
					connectIndices.add(i+1);
				}
			}
		}
		return connectIndices;
	}
	
	private double calcExpectedDistance(CGP g1, CGP g2){
		int d = g1.diff(g2); //hamming distance
		int x = g1.size-d; //similar alleles
		ArrayList<Integer> differingConnections = diffConnectIndices(g1,g2); //set of differing connections
		double m = g1.MUTATION_RATE; //mutation rate
		double F = g1.NUM_FUNCTIONS; //size of function set
		
		double fD = d-differingConnections.size(); //the number of differing functions is the number of differing alleles minus the number of differing connections
		
		double inputs = g1.getInput().length; //number of terminals or inputs
		
		//Series product over differing connections
		double cProduct = 1.0;
		for (Integer c : differingConnections) {
			double ci = c/(g1.arity+1)+inputs-1; //computes the value for each differing connection
			cProduct = cProduct*ci; //takes the product of this and previous values
		}
		
		//binomial power denominator
		double mMinus1Product = Math.pow((1-m),x);
		
		//function numerator
		double fProduct = Math.pow((F-1),fD);
		
		//probability of mutating m values
		double mProduct = Math.pow(m,d);
		
		//final evaluation
		
		double E = fProduct*cProduct/(mMinus1Product*mProduct);
		
		return E;
	}

}
